W4terCTF24 SCC旅游队WP(部分)
MISC
Priv Escape
既然是提权,进去之后 cat /etc/passwd
,发现有一个r00t用户。
sudo -l
,发现W4terCTFPlayer
用户可以无密码以r00t
用户身份运行/usr/sbin/nginx
.
然后发现flag在 /tmp/flag
下,且文件读权限属于 r00t
。
以r00t
身份配置nginx开启一个web服务,输出flag即可。
但没有权限修改/etc/nginx/nginx.conf
,所以在所有用户都可以访问的 /tmp
下创建配置文件,然后 nginx -c
指定之。
但还是会报错,查阅后修改pid
文件路径就行。
1 | pid /tmp/nginx.pid; |
1 | sudo -u r00t /usr/sbin/nginx -c /tmp/nginx.conf |
broken.mp4
搜索找到工具
Revenge of Vigenere
可以猜出的明文有:
- W4terCTF
- 单个字母的单词,只有a(虽然u和i也有可能,但还是这么假设了)
总共14个字母。
读代码可知,
- 密文只与key、字母、位置有关。
- 加密每个字母位置,只使用到key里的其中一个字母。
- 如果知道key的长度,就可以知道加密某个位置使用的是哪个key里的字母。
枚举key长度,然后对这14个已知明文,枚举对应位置的key,复杂度14 * 26 * 10,发现可以稳定排除到只剩下一种key长度。
多开几次终端,直到随机到的key长度为10,发现剩下800种可能的key。
生成800个可能的明文,然后用2字母单词表,排除所有不合法的明文,最后只剩下30个明文,且flag都是相同的。
1 | import copy |
1 | import socket |
1 | import itertools |
Spam 2024
用这玩意解码,得到emoji,然后用emoji aes解码,用🔑作为密码可以解(我们3个人想了3天)。
得到
然后就卡了两天。。发现这个网站有问题,换一个就可以
后面看起来就是base64(异或个数?),ciberchef工具可解。
Sign In
队友做的,我也不知道怎么想到的。。太抽象。
CRYPTO
Wish
生成一个接近114514的随机数就行。发现seed和随机次数都是我们可控的,且范围限制了,枚举可以生成接近114514的组合就行。
1 | from flask import Flask, request, jsonify, send_from_directory |
1 | import requests |
Smoke hints
1 | assert 114514 * x**2 - 11680542514 * y**2 + 1919810== 2034324 |
这个方程化简后,搜索可知叫什么pell方程,有在线工具可解。
1 | x = 34834945635419823491817566563399234823053176449889821571800075702352062905044231520196782430564993617886316750841220280683153456634693274516582390418863033711415731372881163288179660369032440262647344962570809308551786423557604581792293023628226671539671001863522824415876161727357840363896909435994314597682318687286109212360132261705780761350223208855493439905713509683216585447535669179103840355151676900348955850726834778558748576176596609474037298456423607570516459873639794526160082489103786303332253388597560031538949333472681857144605196440020688999368156212067614295618998719682195870452330682061061500341728481458877113934526003865064359452801 |
hint1和hint2,用那个什么费马小定理还是大定理忘了,反正跟取模和阶乘有关的,可以算出 e % hint1
.
hint5 和 pq == n
联立,得到一个一元二次方程,求根公式可解p、q。
接下来只要知道e就能求出d,进而解flag了。由于我们知道e % hint1
,所以枚举e实际上复杂度可以是18位(<1000*1000 = 1e6
),一秒内可解。
1 | import math |
PWN
Remember It 0
1 | from pwn import * |
Remember It 1
发现次数是可以>10的,那么就可以栈溢出,溢出到后门函数就行了。
1 | from pwn import * |
Remember It 2
开了canary那么先泄露出来,用那个打印history的操作可以泄露。但canary第一位固定是\x00,所以得覆盖成别的,才能让字符串不被截断。
然后这题没有后门,只能进libc用system("/bin/sh")
了,所以需要泄露一个libc的地址。想了很多方法都很麻烦,最后想到main的返回地址应该是回到libc的,实验发现确实可以通过main的返回地址推出libc的基地址。
题目给了libc版本,直接可以得到system和 "/bin/sh"
的偏移。然后还需要修改rdi作为system的参数,发现ROPgadget是可以搜到libc里的 pop rdi; ret
的,这样就做完了。
1 | from pwn import * |
MachO Parser
参考w4terctf2023的wp,是可以直接自己构造二进制来生成文件的。
一开始以为栈上没数组,就觉得是堆漏洞,后来一查才发现 alloca
是在函数栈帧上分配内存的(怎么会有这么奇怪的函数)。
那么溢出点就只有 loaded_macho
了。它是通过 seg->filesize
来控制偏移量的。这里就有一个显然的漏洞,alloca分配的是文件大小,而seg->filesize
是我们可控制的量,构造 seg->filesize
就可以利用这个栈溢出。
然而,随便修改seg->filesize
也不可行,因为 memcpy(loaded_macho, data + seg->fileoff, seg->filesize)
会超过data导致段错误(data是mmap分配的)。我的做法是构造多个seg,它们的seg->fileoff
是一样的,这样可以让loaded_macho偏移到我们控制的位置,也可以让memcpy
不访问data外的内容,以及方便控制好文件大小filesize。
溢出后很容易覆盖返回地址为后门函数 command
。但是还需要一个字符串,以及一个rdi来传入 cat /flag
的指针。字符串我们可以写入data,且地址可知(因为mmap指定了)。使用ROPGadget没有搜到 pop rdi; ret
。于是想到libc,但发现即使没开PIE,libc的地址也是会变的,而本题是一次性交互,没办法泄露地址再来搞事情。
既然没法控制rdi那么就回到command看看,发现最后call system的rdi是从rax拿的,而rax又是从rbp偏移的栈上拿的,那么可以控制好rbp,使得rbp+command处是我们在data上提前写入的cat flag的地址,然后直接跳转到call system的前两行。
rbp可以通过覆盖栈上的saved_rbp来控制,这样就可以一次性get shell。(感觉十分优雅
1 | import struct |
WEB
Auto Unserialize
搜到传入phar时,file_exists有反序列化漏洞。
GIF89a文件头可以绕过图片检测。
1 |
|
URL传入:phar://p.phar/test.txt
GitZip
这个地方传入 ../../../../../../../../../../../../
之类的东西就可以访问到根目录,但是测试发现只有严格符合它的规则(3个斜杠)才会被捕获。
然后搜到%2f
似乎可以绕过这种。
PNG Server
加个png或者gif文件头就可以绕过图片检测。
但是图片会被重命名,搜到nginx漏洞,在路径后加入 /.php
就会解析成php。
User Manager
传入两个元素,然后给order_by加上条件,就可以盲注。条件为真增序,假则反序。
1 | import requests |
但是当时没写二分让它慢慢跑导致痛失前3血(flag怎么这么长)
ASHBP
得让cre解密出来是flag。发现可以下载公钥(PUBLIC),直接用下载下来的公钥加密flag和 /tmp/flag
(一开始没看docker file,导致找半天不知道flag在哪。但是做完这个去做priv escape就很快找到 /tmp/flag
Just ReadObject
搜索transform,反序列化,read object等关键词可以找到 “CC2链”。具体地,priority_queue在readobject的时候,会调用其元素的Comparator的compare函数。
模仿CC2链,构造一个priority_queue,指定Comparator,并在Comparator里套娃另一个comparator,就可以实现一个链式的调用。
1 | import java.io.FileOutputStream; |
REVERSE
box
IDA搜索flag、W4terCTF、success、congratula等字符串,可以定位到入口点,很容易发现是一个简单的异或逻辑。
1 | hex_data = "7A 05 6E 01 02 69 54 6B 49 09 58 54 53 67 62 4F 77 50 60 4E 60 63 7A 69 18 79 1C 49 68 4B 15 63 4B 4D 53 45 20 49 5D 5B 74 71 46 71 6C 41 2C 49 4D" |
BruteforceMe
第一层是异或再*17,第二层是base64魔改,第三层是一个加密算法的魔改。
第三层直接逆向,第二层似乎难以逆向,于是直接暴力。
1 | import string |
norr
可以发现逻辑是
主要是func3. 硬刚即可。
1 | def compute_values(int1): |
古老的语言
使用vb decompiler可以反编译。但是开优化的话会有错误(var_B0那里)
然后一直以为leftRotate是左旋转,后面找到一个工具VBDEC.exe去调试pcode,才知道是单纯位移。
1 |
|
Crabs
第一层是异或。第二层是按照每个字母的顺序生成一个矩阵。第三层是矩阵乘法。
第三层求逆矩阵,第二层IDA搜索找出原数组,第一层异或回去。
1 | import numpy as np |
安安又卓卓
拖进jadx。
第一层是石头剪刀布,对面的出拳是写死的。第二层去逆向那个数字就行。第三层混淆了但没完全混淆。
1 | right_choice = ['2', '3', '2', '2', '2', '2', '2', '3', '2', '1', '1', '2', '1', '1', '1', '2', '2', '3', '1', '2', '2', '3', '2', '2', '2', '3', '1', '3', '2', '2', '1', '2', '2', '1', '3', '2', '1', '1', '3', '3', '2', '3', '3', '2', '3', '2', '2', '3', '2', '3', '1', '2', '2', '3', '2', '2', '2', '3', '2', '3', '3', '3', '1', '3', '2', '1', '3', '2', '1', '2', '2', '3', '2', '3', '3', '3', '2', '2', '3', '3', '2', '3', '2', '1', '1', '3', '1', '1', '2', '3', '3', '3', '2', '3', '3', '2', '2', '3', '3', '2', '2', '3', '2', '3', '2', '1', '3', '3', '2', '2', '1', '2', '2', '1', '1', '1', '1', '2', '2', '1', '2', '1', '2', '1', '3', '3', '1', '3', '2', '3', '1', '2', '1', '2', '2', '1', '2', '1', '3', '2', '3', '1', '1', '2', '2', '3', '1', '3', '2', '1', '2', '2', '2', '1', '3', '2', '2', '1', '2', '3', '2', '3', '1', '3', '2', '2', '1', '2', '2', '3', '1', '2', '2', '1', '2', '1', '2', '3', '3', '3', '2', '2', '3', '1', '2', '1', '1', '1', '2', '1', '2', '2', '2', '3', '1', '2', '1', '2', '2', '1', '2', '1', '3', '2', '3', '3', '3', '2', '2', '3', '1', '2', '2', '3', '1', '3', '2', '1', '2', '1', '3', '1', '3', '3', '2', '1', '3', '2', '2', '2', '2', '1', '2', '1', '3', '2', '1', '1', '1', '2', '2', '1', '3', '2', '2', '1', '2', '2', '2', '1', '2', '3', '1', '3', '3', '3'] |
1 | def add(a, b): return a + b |
1 | def add(a, b): return a + b |
DouDou
使用工具反混淆得到
1 | const M = [[99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, 118], [202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, 164, 114, 192], [183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, 113, 216, 49, 21], [4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, 235, 39, 178, 117], [9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, 41, 227, 47, 132], [83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, 74, 76, 88, 207], [208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, 80, 60, 159, 168], [81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, 33, 16, 255, 243, 210], [205, 12, 19, 236, 95, 151, 68, 23, 196, 167, 126, 61, 100, 93, 25, 115], [96, 129, 79, 220, 34, 42, 144, 136, 70, 238, 184, 20, 222, 94, 11, 219], [224, 50, 58, 10, 73, 6, 36, 92, 194, 211, 172, 98, 145, 149, 228, 121], [231, 200, 55, 109, 141, 213, 78, 169, 108, 86, 244, 234, 101, 122, 174, 8], [186, 120, 37, 46, 28, 166, 180, 198, 232, 221, 116, 31, 75, 189, 139, 138], [112, 62, 181, 102, 72, 3, 246, 14, 97, 53, 87, 185, 134, 193, 29, 158], [225, 248, 152, 17, 105, 217, 142, 148, 155, 30, 135, 233, 206, 85, 40, 223], [140, 161, 137, 13, 191, 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22]], |
然后从后往前逆。
那个I有点难解密,就尝试了一下套娃I(I(x)), 发现是对称的。
1 | t = ['4b000aec37eca4a5adb8a52427dcb15d', |
以及每逆出一层就去console里试试对不对。
Shuffle Puts
打开IDA搜索W4terCTF。
W4terCTF24 SCC旅游队WP(部分)